package com.zjl.redis.第16章_分布式锁;

import cn.hutool.core.util.IdUtil;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import redis.clients.jedis.Jedis;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * ClassName: RedisLock
 * Package: com.zjl.redis.第16章_分布式锁
 * Description:
 *
 * @Author 张蛟龙
 * @Create 2024/7/9 21:54
 * @Version 1.0
 */
public class RedisLock implements Lock{
    private static String lockLua = """
                        if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
                             redis.call('hincrby',KEYS[1],KEYS[2],'1')
                             redis.call('expire',KEYS[1],KEYS[3])
                             return 1
                        else
                             return 0
                        end
            """;
    private static String unlockLua = """
                    if redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
                        if redis.call('hincrby',KEYS[1],KEYS[2],'-1') == 0 then
                             return redis.call('del',KEYS[1])
                        else
                             return 1
                        end
                    else
                        return 0
                    end
            """;
    private static String addTimeLua = """
                    if redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
                        redis.call('expire',KEYS[1],KEYS[3])
                        return 1
                    else
                        return 0
                    end
            """;

    private Lock lock;

    private long TIME = 50L;//超时时间  默认50 秒
    private String lockName;//判断哪种锁
    private static final String id;//为了标识唯一的程序
    static {
        id = UUID.randomUUID().toString();//java自带
//        id = IdUtil.simpleUUID();//hutool  工具包的，都行
    }
    public static String getValue(){
        return id + Thread.currentThread().getName() + Thread.currentThread().getId();
    }
    public RedisLock(RedisCommands<String, String> redis,String lockName){
        lock = new RedisRedisCommandsLock(redis);
        this.lockName = lockName;
    }
    public RedisLock(Jedis redis,String lockName){
        lock = new RedisJedisLock(redis);
        this.lockName = lockName;
    }
    public RedisLock(RedisTemplate<String,Object> redis,String lockName){
        lock = new RedisRedisTemplateLock(redis);
        this.lockName = lockName;
    }

    class RedisRedisCommandsLock implements Lock{
        private RedisCommands<String, String> redis;
        private long time = TIME;
        private Thread addTime;
        public RedisRedisCommandsLock(RedisCommands<String, String> redis){

            this.redis = redis;
        }

        @Override
        public void lock() {
            tryLock();
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new InterruptedException("？？？");
        }

        @Override
        public boolean tryLock() {
            try {
                return tryLock(TIME,TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            this.time = time;
            long begin = System.currentTimeMillis();
            String value = getValue();

            boolean b;
            do {
                if(time >0){
                    long c = System.currentTimeMillis()-begin;
                    if(c/1000 > time){
                        throw new InterruptedException("超时未获取");
                    }
                    b = redis.eval(lockLua,ScriptOutputType.BOOLEAN,lockName, value,String.valueOf(time));
                }else{
                    b = redis.eval(lockLua,ScriptOutputType.BOOLEAN,lockName, value,String.valueOf(Integer.MAX_VALUE));
                }
            }while(!b);
            System.out.println("加时" + value);

            if(addTime == null){
                getAddThread(time, value);
            }
            return true;
        }

        private void getAddThread(long time, String value) {
            addTime = new Thread(() -> {
                do{
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }while(redis.eval(addTimeLua, ScriptOutputType.BOOLEAN, lockName, value, String.valueOf(time)));
            });
            addTime.start();
        }

        @Override
        public void unlock() {
            String lua = """
                    if redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
                        if redis.call('hincrby',KEYS[1],KEYS[2],'-1') == 0 then
                             return redis.call('del',KEYS[1])
                        else
                             return 1
                        end
                    else
                        return 0
                    end
                """;
            String value = getValue();
            redis.eval(lua, ScriptOutputType.BOOLEAN, lockName, value);
        }

        @Override
        public Condition newCondition() {
            return null;
        }
    }
    class RedisJedisLock implements Lock{
        private Jedis redis;
        private Thread addTime;
        private long time = TIME;
        public RedisJedisLock(Jedis redis){
            this.redis = redis;

        }
        @Override
        public void lock() {
            tryLock();
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new InterruptedException("？？？");
        }

        @Override
        public boolean tryLock() {
            try {
                return tryLock(TIME,TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            long begin = System.currentTimeMillis();
            String value = getValue();

            String b;
            do {
                if(time >0){
                    long c = System.currentTimeMillis()-begin;
                    if(c/1000 > time){
                        throw new InterruptedException("超时未获取");
                    }
                    b = String.valueOf(redis.eval(lockLua,3,lockName, value,String.valueOf(time)));
                }else{

                    b = String.valueOf(redis.eval(lockLua,3,lockName, value,String.valueOf(Integer.MAX_VALUE)));
                }
            }while("0".equals(b));
            if(addTime == null){
                getAddThread(time, value);
            }
            return true;
        }
        private void getAddThread(long time, String value) {
            addTime = new Thread(() -> {
                while ("1".equals(redis.eval(addTimeLua,3,lockName, value,String.valueOf(time)))){
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                };
            });
            addTime.start();
        }
        @Override
        public void unlock() {

            String value = getValue();
            redis.eval(unlockLua, 2,lockName, value);
        }

        @Override
        public Condition newCondition() {
            return null;
        }
    }
    class RedisRedisTemplateLock implements Lock{
        private RedisTemplate<String,Object> redis;
        private Thread addTime;
        private long time = TIME;
        public RedisRedisTemplateLock(RedisTemplate<String,Object> redis){
            this.redis = redis;

        }
        @Override
        public void lock() {
            tryLock();
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new InterruptedException("???");
        }

        @Override
        public boolean tryLock() {
            try {
                return tryLock(TIME,TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            String value = getValue();
            long begin = System.currentTimeMillis();
            Boolean b;
            do {
                if(time > 0){
                    long c = System.currentTimeMillis()-begin;
                    if(c/1000 > time){
                        throw new InterruptedException("超时未获取");
                    }
                    b = redis.execute(new DefaultRedisScript<>(unlockLua,Boolean.class),
                            Arrays.asList(lockName, value,String.valueOf(time))
                    );
                }else{
                    b = redis.execute(new DefaultRedisScript<>(unlockLua,Boolean.class),
                            Arrays.asList(lockName, value,String.valueOf(Integer.MAX_VALUE))
                    );
                }

            }while(!b);
            if(addTime == null){
                getAddThread(time, value);
            }
            return true;
        }
        private void getAddThread(long time, String value) {
            addTime = new Thread(() -> {
                Boolean b;
                do{
                    try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}
                    b = redis.execute(new DefaultRedisScript<>(addTimeLua,Boolean.class),
                            Arrays.asList(lockName, value,String.valueOf(time)));
                }while(b);
            });
            addTime.start();
        }
        @Override
        public void unlock() {

            String value = getValue();
            redis.execute(new DefaultRedisScript<>(unlockLua,Boolean.class),
                    Arrays.asList(lockName, value)
            );
        }

        @Override
        public Condition newCondition() {
            return null;
        }
    }


    @Override
    public void lock() {
        lock.lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        lock.lockInterruptibly();
    }

    @Override
    public boolean tryLock() {
        return lock.tryLock();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return lock.tryLock(time,unit);
    }

    @Override
    public void unlock() {
        lock.unlock();
    }

    @Override
    public Condition newCondition() {
        return lock.newCondition();
    }

}
